use ordered_float::NotNan;
use super::str_suffix::{Iter,StrSuffix};

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum Token {
    IntLiteral(i64),
    FloatLiteral(NotNan<f64>),
    Add,
    Sub,
    Mul,
    Div,
    LParen,
    RParen,
    EOF
}

pub struct Tokenizer<'a> {
    input:&'a str,
    chars:Iter<'a>,
    read_index:usize
}

impl<'a> Tokenizer<'a> {
    pub fn new(input:&'a str) -> Self {
        Tokenizer {
            input,
            chars: StrSuffix::new(input).iter(),
            read_index:0
        }
    }

    fn bump(&mut self) -> Option<(usize,u8)> {
        self.chars.next().map(|ch| {
            let cur = self.read_index;
            if self.read_index < self.input.len() {
                self.read_index += 1;
            }
            (cur,ch)
        })
    }

    fn lookahead(&self) -> Option<(usize,u8)> {
        self.chars.as_str_suffix().first().map(|b| (self.read_index, b))
    }

    fn test_lookahead<F>(&self, mut test: F) -> bool where F: FnMut(u8) -> bool {
        self.lookahead().map_or(false, |(_,ch)| test(ch))
    }

    fn slice(&self, start: usize, end: usize) -> &'a str {
      
        &self.input[start..end]
    }

    fn take_until<F>(&mut self,start:usize, mut terminate: F) -> (usize, &'a str) where F: FnMut(u8) -> bool {
        while let Some((end, ch)) = self.lookahead() {
            if terminate(ch) {
                return (end, self.slice(start, end));
            } else {
                self.bump();
            }
        }
        (start, self.slice(start, self.read_index))
    }

    fn take_while<F>(&mut self, start: usize, mut keep_going: F) -> (usize, &'a str)  where F: FnMut(u8) -> bool {
        self.take_until(start, |c| !keep_going(c))
    }

    fn numeric_literal(&mut self,start:usize) -> Token {
        let (_, int) = self.take_while(start, is_digit);
        match self.lookahead() {
            Some((_, b'.')) => {
                self.bump(); // Skip b'.'
                let (_, float) = self.take_while(start, is_digit);
                return Token::FloatLiteral(NotNan::new(float.parse().unwrap()).unwrap())
            },
            None | Some(_) => {
                if let Ok(val) = int.parse() {
                    return Token::IntLiteral(val)
                } else {
                    panic!("err parse number ! {} ,{}",int,self.read_index);
                }
            }
        };
    }

    
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Token;
    fn next(&mut self) -> Option<Token> {
        let emit = |tok | {
            Some(tok)
        };
        while let Some((start,chr)) = self.bump() {
            match chr {
                b'(' => return emit(Token::LParen),
                b')' => return emit(Token::RParen),
                b'+' => return emit(Token::Add),
                b'-' => return emit(Token::Sub),
                b'*' => return emit(Token::Mul),
                b'/' => return emit(Token::Div),
                ch if is_digit(ch) || (ch == b'-' && self.test_lookahead(is_digit)) => {
                    return emit(self.numeric_literal(start));
                },
                ch if (ch as char).is_whitespace() => continue,
                _ => panic!("eror chr {}",chr)
            }
        }
       None
    }
}

fn is_digit(ch: u8) -> bool {
    (ch as char).is_digit(10)
}




